﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using AccessControl.FS84Test.EnumAndStruct;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;

namespace AccessControl.FS84Test
{

    class FS84Commiunicator
    {
        public int curRow; //delete this
        #region Events
        public event FingerPrintDetectedEventHandler FingerPrintDetected;
        public event EventHandler WaitTimeoutExpired;
        public event EventHandler ConnectionError;
        #endregion

        #region fields
        private static Socket _socket;
        private IPEndPoint _endPoint;
        private bool isWaitingForFinger;
        private bool isbusy;
        private bool isSendReceive;
        private int lastFingerDetectedTime;
        private int checkInterval = 50;
        #endregion

        #region properties
        public bool EnableIdleOptimization { get; set; }
        public ErrorCodes LastErrorCode { get; set; }
        public string lastErrorMessage { get; set; }
        #endregion

        #region constructors
        public FS84Commiunicator()
        {
        }
        //public FS84Commiunicator(string ipString, int port)
        //    : this()
        //{
        //    Connect(ipString, port);
        //}
        #endregion

        #region methods
        private FileStream fs;
        StreamWriter sw;

        public bool CheckFinger(out int contrast)
        {
            bool retVal = false;
            int tempParam2 = 0;
            contrast = 0;
            retVal = (LastErrorCode = CommiunicateWithFAC(Commands.CheckFinger, ref contrast, ref tempParam2, 0x00, null, null)) == ErrorCodes.RESULT_OK;

            if (retVal)
            {
                lastFingerDetectedTime = Environment.TickCount;
                checkInterval = 50;
            }
            else
            {
                if (EnableIdleOptimization)
                {
                    if (lastFingerDetectedTime < 60000)
                        checkInterval = 30;
                    else if (lastFingerDetectedTime < 600000)
                        checkInterval = 200;
                    else if (lastFingerDetectedTime < 3600000)
                        checkInterval = 500;
                    else
                        checkInterval = 1000;
                }
                else checkInterval = 30;
            }

            return retVal;

        }

        public bool CaptureImage(out int contrast, out int whitePixels, out byte dosage)
        {
            bool retVal = false;
            contrast = 0;
            whitePixels = 0;
            byte command = (byte)Commands.CaptureImage;
            retVal = (LastErrorCode = CommiunicateWithFAC(ref command, ref contrast, ref whitePixels, 0x00, null, null)) == ErrorCodes.RESULT_OK;
            dosage = command;
            return retVal;
        }


        public byte[] CaptureAndDownloadImage(bool checkFinger, out int contrast, out int whitePixels, out byte dosage)
        {
            while (isbusy) Thread.Sleep(0);
            isbusy = true;


            whitePixels = 0;
            contrast = 0;
            dosage = 0;
            byte[] rawImage = null;
            if (!checkFinger || CheckFinger(out contrast))
            {
                CaptureImage(out contrast, out whitePixels, out dosage);

                if (contrast > 1000)
                {
                    Beep(Beeps.Normal, false);

                    rawImage = DownloadRawImage();
                }
            }

            isbusy = false;

            return rawImage;
        }

        private byte[] DownloadRawImage()
        {

            int imageWidth = 320;
            int imageHeight = 480;
            int totalBytes = imageWidth * imageHeight;
            var RAWImage = new byte[totalBytes];


            int param1 = 0;
            int param2 = totalBytes;

            if ((LastErrorCode = CommiunicateWithFAC(Commands.DownloadRAWImage, ref param1, ref param2, 0x00, null, RAWImage)) == ErrorCodes.RESULT_OK)
            {
                //nothing, if false the rawimage would be null
            }

            return RAWImage;


        }

        public byte[] DownloadCurrentSample()
        {
            int imageWidth = 320;
            int imageHeight = 480;
            int totalBytes = imageWidth * imageHeight;
            byte[] RAWImage = new byte[665];


            int param1 = 0;
            int param2 = 0;

            if ((LastErrorCode = CommiunicateWithFAC(Commands.DownloadCurrentSample, ref param1, ref param2, 0x00, null, RAWImage)) == ErrorCodes.RESULT_OK)
            {
                //nothing, if false the rawimage would be null
            }

            return RAWImage;
        }

        public void WaitForFingerprint(int timeout)
        {
            isWaitingForFinger = true;
            Thread waitThread = new Thread(new ParameterizedThreadStart(WaitForFingerprint));
            waitThread.Start(timeout);
        }
        public void StopWaitingForFinger()
        {
            isWaitingForFinger = false;
        }
        private void WaitForFingerprint(object data)
        {
            int startTicks = Environment.TickCount;
            var timeout = (int)data;
            var args = new FingerPrintDetectedEventArgs();
            bool Retry = true;
            while (Retry)
            {
                args.rawImage = CaptureAndDownloadImage(true, out args.contrast, out args.whitePixels, out args.dosage);

                Retry = isWaitingForFinger && args.rawImage == null;

                if (Retry) Thread.Sleep(100);

                if (timeout > 0 && Environment.TickCount > timeout + startTicks)
                {
                    WaitTimeoutExpired(this, EventArgs.Empty);
                    return;
                }
            }


            if (isWaitingForFinger)
            {
                isWaitingForFinger = false;
                FingerPrintDetected(this, args);
            }
        }

        public void Beep(Beeps beepType)
        {
            Beep(beepType, true);
        }

        private void Beep(Beeps beepType, bool setBusy)
        {
            if (setBusy)
            {
                while (isbusy) Thread.Sleep(0);
                isbusy = true;
            }


            int repeats = 0;
            int duration = 30;
            int delay = 30;
            switch (beepType)
            {
                case Beeps.Normal:
                    repeats = 1;
                    duration = 30;
                    break;
                case Beeps.DoubleShort:
                    repeats = 2;
                    delay = 50;
                    duration = 50;
                    break;
                case Beeps.DoubleLong:
                    repeats = 2;
                    delay = 200;
                    duration = 300;
                    break;
                case Beeps.Triple:
                    repeats = 3;
                    delay = 80;
                    duration = 50;
                    break;
                case Beeps.testLong:
                    repeats = 4;
                    delay = 1000;
                    duration = 100;
                    break;
            }
            for (int r = 1; r <= repeats; r++)
            {
                SwitchDevicePeripheral(DevicePeripherals.Buzzer, true);
                Thread.Sleep(duration);
                SwitchDevicePeripheral(DevicePeripherals.Buzzer, false);
                if (r < repeats) Thread.Sleep(delay);
            }

            if (setBusy) isbusy = false;
        }

        public bool SwitchDevicePeripheral(DevicePeripherals peripheral, bool IsOn)
        {
            bool param1, param2;
            return SwitchDevicePeripheral(peripheral, IsOn, out param1, out param2);
        }
        public bool SwitchDevicePeripheral(DevicePeripherals peripheral, bool IsOn, out bool DoorSensor0, out bool DoorSensor1)
        {
            bool retVal = false;

            int param1 = (int)(IsOn ? peripheral : 0x00000000);
            int param2 = (int)(IsOn ? 0x00000000 : peripheral);

            DoorSensor0 = false;
            DoorSensor1 = false;

            if ((LastErrorCode = CommiunicateWithFAC(Commands.SwitchPeripheral, ref param1, ref param2, 0x00, null, null)) == ErrorCodes.RESULT_OK)
            {
                retVal = true;
                DoorSensor0 = (param1 & 1) > 0;
                DoorSensor1 = (param1 & 2) > 0;
            }
            else
            {
            }

            return retVal;

        }
        public void CancelCommand()
        {
            int param1 = 0;
            int param2 = 0;
            CommiunicateWithFAC(Commands.Cancel, ref param1, ref param2, 0x00, null, null);
        }
        public int GetFreeMemory()
        {
            int param1 = 0;
            int param2 = 0;
            CommiunicateWithFAC(Commands.GetFreeMemory, ref param1, ref param2, 0x00, null, null);
            return param1;
        }
        public void EraseAllTemplates()
        {
            int param1 = 0;
            int param2 = 0;
            CommiunicateWithFAC(Commands.EraseAllTemplates, ref param1, ref param2, 0x00, null, null);
        }
        public void Reboot()
        {
            _socket.Send(new byte[] { 0x40, (byte)Commands.Reboot, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x0D });
            //while (_socket.Connected) { Thread.Sleep(500); }
            Connect();
        }
        public int ProcessCapturedSample()
        {
            int param1 = 0;
            int param2 = 0;
            CommiunicateWithFAC(Commands.ProcessCapturedFinger, ref param1, ref param2, 0x00, null, null);
            return param1;
        }

        public ErrorCodes CommiunicateWithFAC(Commands Command, ref int param1, ref int param2, byte flag, byte[] SendBuffer, byte[] ReceiveBuffer)
        {
            byte command = (byte)Command;
            return CommiunicateWithFAC(ref command, ref param1, ref param2, flag, SendBuffer, ReceiveBuffer);
        }

        public ErrorCodes CommiunicateWithFAC(ref byte Command, ref int param1, ref int param2, byte flag, byte[] SendBuffer, byte[] ReceiveBuffer)
        {

            while (isSendReceive) Thread.Sleep(0);
            isSendReceive = true;

            ErrorCodes retVal;

            bool successed = false;
            int retries = 0;
            byte[] param = new byte[13];
            byte[] buffer = new byte[13];
            param[0] = 0x40;
            param[1] = (byte)Command;
            BitConverter.GetBytes(param1).CopyTo(param, 2);
            BitConverter.GetBytes(param2).CopyTo(param, 6);
            param[10] = flag;
            param[11] = CalculateChecksum(param);
            param[12] = 0x0D;
            do
            {
                retries++;
                if (Send(param, param.Length, 3000))
                {
                    if (Receive(buffer, buffer.Length, 3000))
                    {
                        if ((ErrorCodes)buffer[0] == ErrorCodes.RESULT_OK)
                        {
                            param1 = BitConverter.ToInt32(buffer, 2);
                            param2 = BitConverter.ToInt32(buffer, 5);

                            if (CalculateChecksum(buffer) == buffer[11])
                            {
                                successed = true;
                            }
                        }
                    }

                }
                else
                    retVal = ErrorCodes.RESULT_WINSOCK_ERROR;
            } while (!successed && retries < 3);

            if (successed)
            {
                byte[] eof = new byte[2];
                switch ((Commands)Command)
                {
                    case Commands.DownloadRAWImage:

                        if (Receive(ReceiveBuffer, ReceiveBuffer.Length, 10000))
                        {
                            Receive(eof, eof.Length, 2000);
                            var CRCError = CalculateChecksum(ReceiveBuffer) != eof[0];
                            successed = !CRCError;
                        }
                        break;
                    case Commands.DownloadCurrentSample:
                        if (Receive(ReceiveBuffer, ReceiveBuffer.Length, 10000))
                        {
                            Receive(eof, 1, 2000);
                            //var CRCError = CalculateChecksum(ReceiveBuffer) != eof[0];
                            //successed = !CRCError;
                            successed = true;
                        }
                        break;
                }

                Command = buffer[1];
            }

            if (!successed) retVal = ErrorCodes.RESULT_WINSOCK_ERROR;
            else retVal = (ErrorCodes)buffer[10];

            isSendReceive = false;
            return retVal;
        }

        private bool Connect()
        {
            try
            {
                _socket = new Socket(_endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                _socket.NoDelay = true;
                _socket.Connect(_endPoint);
            }
            catch (Exception ex)
            {
                lastErrorMessage = ex.Message;
                return false;
            }
            return _socket.Connected;
        }

        public bool Connect(string ipString, int port)
        {
            if (_socket == null || !_socket.Connected)
            {
                var ipAddress = IPAddress.Parse(ipString);
                _endPoint = new IPEndPoint(ipAddress, port);
                return Connect();
            }
            else return true;
        }

        private byte CalculateChecksum(byte[] array)
        {
            int checksum = 0;
            for (int i = 0; i < 11; i++)
                checksum += array[i];
            return (byte)(checksum % 256);
        }

        private bool Send(byte[] buffer, int size, int timeout)
        {
            FlushData();

            int startTickCount = Environment.TickCount;
            int sent = 0;  // how many bytes is already sent
            do
            {
                try
                {
                    if (timeout > 0)
                        _socket.ReceiveTimeout = Math.Max(0, timeout - (Environment.TickCount - startTickCount));
                    if (timeout > 0 && Environment.TickCount > startTickCount + timeout)
                        break;

                    sent += _socket.Send(buffer, sent, size - sent, SocketFlags.None);
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        // socket buffer is probably full, wait and try again
                        Thread.Sleep(30);
                    }
                    else if (ex.SocketErrorCode == SocketError.ConnectionReset ||
                       ex.SocketErrorCode == SocketError.NetworkReset)
                    {
                        Thread.Sleep(30);
                        Connect();
                    }
                    else if (ex.SocketErrorCode == SocketError.TimedOut)
                        break;
                    else if (ex.SocketErrorCode == SocketError.Interrupted)
                        break;
                    else
                        break;
                }
            } while (sent < size);

            return true;
        }


        private bool Receive(byte[] buffer, int size, int timeout)
        {
            bool retVal = false;

            int startTickCount = Environment.TickCount;
            int received = 0;  // how many bytes is already received
            do
            {
                try
                {
                    if (timeout > 0)
                        _socket.ReceiveTimeout = Math.Max(0, timeout - (Environment.TickCount - startTickCount));

                    while (_socket.Available == 0 && !(timeout > 0 && Environment.TickCount > startTickCount + timeout))
                    {
                        Thread.Sleep(0);
                    }
                    if (timeout > 0 && Environment.TickCount > startTickCount + timeout)
                    {
                        //_socket.Send(new byte[] { 0x40, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0x0D });
                        //_socket.Receive(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
                        break;
                    }
                    received += _socket.Receive(buffer, received, size - received, SocketFlags.None);
                    if (received < size)
                        SendKeepAlive();
                    else
                        retVal = true;
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        // socket buffer is probably empty, wait and try again
                        Thread.Sleep(30);
                    }
                    else if (ex.SocketErrorCode == SocketError.ConnectionReset ||
                      ex.SocketErrorCode == SocketError.NetworkReset)
                    {
                        Thread.Sleep(30);
                        Connect();
                    }
                    else if (ex.SocketErrorCode == SocketError.TimedOut)
                        break;
                    else if (ex.SocketErrorCode == SocketError.Interrupted)
                        break;
                    else
                        break;
                }
            } while (received < size);

            if (_socket.Available != 2 && size > 13)
            {
                int m = _socket.Available;
                int b = 2;
            }

            return retVal;
        }

        private void SendKeepAlive()
        {
            Send(new byte[1] { 0 }, 1, 0);
        }

        public void FlushData()
        {
            var buffer = new byte[1000];
            while (_socket.Available > 0)
                _socket.Receive(buffer);
        }
        #endregion

    }
}
